/**
 * 
 */
package com.fnic.pearl.scheduler;

import java.util.Calendar;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.log4j.Logger;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;

import com.fnic.pearl.scheduler.action.GenericTaskIssueServlet;
import com.fnic.pearl.scheduler.action.MProbeInfoServlet;
import com.fnic.pearl.scheduler.action.MProbeListServlet;
import com.fnic.pearl.scheduler.action.MProbeUpdateServlet;
import com.fnic.pearl.scheduler.action.MSchedulerVersionServlet;
import com.fnic.pearl.scheduler.action.MTaskInfoServlet;
import com.fnic.pearl.scheduler.action.MTaskStatsServlet;
import com.fnic.pearl.scheduler.action.MsgInterfaceInfo;
import com.fnic.pearl.scheduler.action.ProbeHBServlet;
import com.fnic.pearl.scheduler.action.ProbeLoginServlet;
import com.fnic.pearl.scheduler.action.ProbeLogoutServlet;
import com.fnic.pearl.scheduler.action.ProbeTestTaskStatusServlet;
import com.fnic.pearl.scheduler.action.TestTaskIssueServlet;
import com.fnic.pearl.scheduler.action.VersionUpdateServlet;
import com.fnic.pearl.scheduler.action.VersionUpdateStatusServlet;
import com.fnic.pearl.scheduler.constant.SchedulerUrl;
import com.fnic.pearl.scheduler.dao.SchedulerStore;
import com.fnic.pearl.scheduler.mod.ProbeStatusManager;
import com.fnic.pearl.scheduler.model.ProbeStatus;

/**
 * 作为服务端启动的线程
 * 
 * @author HuHaiyang
 * @date 2013年7月15日
 */
public class ServerStarter implements Runnable
{

    private static Logger LOG = Logger.getLogger(ServerStarter.class);

    /**
     * @see java.lang.Runnable#run()
     */
    public void run()
    {
        LOG.info("start server ...");
        SchedulerConfig config = SchedulerConfig.getInstance();

        try
        {
            Resource res = Resource.newClassPathResource("scheduler-ws.xml");
            XmlConfiguration conf = new XmlConfiguration(res.getInputStream());
            Server server = (Server) conf.configure();

            // handler structure
            HandlerCollection handlers = new HandlerCollection();
            ServletHandler handler = new ServletHandler();
            handler.addServletWithMapping(ProbeHBServlet.class, SchedulerUrl.HEARTBEAT);
            handler.addServletWithMapping(ProbeLoginServlet.class, SchedulerUrl.LOGIN);
            handler.addServletWithMapping(ProbeLogoutServlet.class, SchedulerUrl.LOGOUT);
            handler.addServletWithMapping(ProbeTestTaskStatusServlet.class, SchedulerUrl.TESTASK_STATUS);
            handler.addServletWithMapping(TestTaskIssueServlet.class, SchedulerUrl.TESTASK_ISSUE);
            handler.addServletWithMapping(VersionUpdateServlet.class, SchedulerUrl.VERSION_UPDATE);
            handler.addServletWithMapping(VersionUpdateStatusServlet.class, SchedulerUrl.VERSION_UPDATE_STATUS);
            handler.addServletWithMapping(GenericTaskIssueServlet.class, SchedulerUrl.TASK_ISSUE);

            handler.addServletWithMapping(MTaskInfoServlet.class, SchedulerUrl.M_TASK_INFO);
            handler.addServletWithMapping(MProbeListServlet.class, SchedulerUrl.M_PROBE_LIST);
            handler.addServletWithMapping(MTaskStatsServlet.class, SchedulerUrl.M_TASK_STATS);
            handler.addServletWithMapping(MProbeUpdateServlet.class, SchedulerUrl.M_PROBE_UPDATE);
            handler.addServletWithMapping(MSchedulerVersionServlet.class, SchedulerUrl.M_SCHEDULER_VERSION);
            handler.addServletWithMapping(MProbeInfoServlet.class, SchedulerUrl.M_PROBE_INFO);
            handler.addServletWithMapping(MsgInterfaceInfo.class, SchedulerUrl.MSG_INTERFACE_INFO);
            handlers.addHandler(handler);
            // resource handler
            ResourceHandler resourceHandler = new ResourceHandler();
            // resourceHandler.setDirectoriesListed(true);
            // resourceHandler.setWelcomeFiles(new String[] { "index.html" });
            resourceHandler.setResourceBase("./web/");
            handlers.addHandler(resourceHandler);

            // request log
            NCSARequestLog requestLog = new NCSARequestLog(config.getRequestLogFileName());
            requestLog.setRetainDays(config.getRetainDays());
            requestLog.setLogTimeZone(config.getRequestLogTimezone());
            requestLog.setLogDateFormat(config.getRequestLogDateFormat());
            requestLog.setExtended(true);
            RequestLogHandler handlerReqLog = new RequestLogHandler();
            handlerReqLog.setRequestLog(requestLog);
            handlers.addHandler(handlerReqLog);

            server.setHandler(handlers);

            // 启动 probe 连接检查定时器
            new Timer().schedule(new ProbeLinkCheckTask(), 1000, 1000);

            server.start();
            // 异步变同步
            server.join();
        }
        catch (ConfigurationException e1)
        {
            LOG.error("server config exception: " + e1.getMessage());
        }
        catch (Exception e)
        {
            LOG.error("server exception: " + e.getMessage());
        }
    }

    /**
     * 主要负责对各个 probe 连接超时进行检查 如果连接超时（当前主要是通过心跳来判断） 则设置探针状态为 logout，同时上报至 管理节点
     * 
     * @author HuHaiyang
     * @date 2013年10月9日
     */
    class ProbeLinkCheckTask extends TimerTask
    {

        @Override
        public void run()
        {
            final long checkTimeout = SchedulerConfig.getInstance().getProbeCheckTimeout() * 1000;
            final long timeout = SchedulerConfig.getInstance().getHbTimeout() * 1000 + checkTimeout;
            Calendar calNow = Calendar.getInstance();
            Calendar calLast = Calendar.getInstance();
            List<ProbeStatus> lstProbeStatus = ProbeStatusManager.getInstance().getAll();
            for (ProbeStatus ps : lstProbeStatus)
            {
                if (ps != null && ps.getStatus() == ProbeStatus.S_LOGIN)
                {
                    if (ps.getLastRecvHBTime() != null)
                    {
                        calLast.setTime(ps.getLastRecvHBTime());
                    }
                    else
                    {
                        calLast.setTime(ps.getLastLoginTime());
                    }

                    long interval = calNow.getTimeInMillis() - calLast.getTimeInMillis();
                    boolean logout = false;
                    if (!ps.isHandlingHB())
                    {
                        if (ps.getLastRecvHBTime() != null)
                        {
                            // 客户端停止心跳，且之前发送过心跳
                            // 则比较当前与上一次心跳之间的超时时长
                            if (interval > timeout)
                            {
                                // LOG.info("probe [" + ps.getProbeId() + "] pause heartbeat logout.");
                                LOG.info("探针 [" + ps.getProbeId() + "]心跳超时");
                                logout = true;
                            }
                        }
                        else if (interval > timeout)
                        {
                            // 从未接收过心跳
                            LOG.info("探针 [" + ps.getProbeId() + "]没有心跳");
                            logout = true;
                        }
                    }
                    else
                    {
                        // 在处理 heartbeat 的过程中
                        // probe break, or network fault
                        // 应该小于心跳超时时长+TIMEOUT
                        if (interval > timeout)
                        {
                            LOG.info("探针 [" + ps.getProbeId() + "]心跳超时");
                            logout = true;
                        }
                    }

                    if (logout)
                    {
                        LOG.info("探针心跳异常，将其退出控制器 [" + ps.getProbeId() + "]");
                        ProbeStatusManager.getInstance().updateStatus(ps.getProbeId(), ProbeStatus.S_LOGOUT);
                        // 同时还将此探针上的任务都设置为 hangup 状态
                        // 而在设置为 hangup 状态的同时，
                        // 如果该探针又来登录以及发送任务状态，则会造成状态的不一致
                        // 因此对于此操作需要同步控制
                        SchedulerStore.getInstance().switchTaskToHangUp(ps.getProbeId());;
                    }
                }
            }
        }
    }
}
